﻿/************************************************************************
* 类名：CHttpTransfer
* 功能：基于Socket的WEB通信类；用于WEB数据包收发、WEB服务器通信。
* 作者：泡面
* 时间：2011-02-18
* 版本：1.0.11.218
************************************************************************/
#define nMAX_URL_LEN	2048
#define nMAX_PACKET_LEN 4096

#pragma once
#include <stdio.h>
#include <WinSock2.h>
#include <vector>
#include <string>

#pragma comment(lib, "Ws2_32.lib")

using namespace std;
class CHttpTransfer
{
public:
	inline CHttpTransfer(void)
	{
		memset(CHttpTransfer::m_lpHostName, 0, nMAX_URL_LEN);
		memset(CHttpTransfer::m_lpMethod, 0, 4);
		memset(CHttpTransfer::m_lpUrl, 0, nMAX_URL_LEN);
		memset(CHttpTransfer::m_lpHeaders, 0, nMAX_PACKET_LEN);
	}

	inline virtual ~CHttpTransfer(void)
	{
	}

public:

	/*! @InitHttp
	********************************************************************************
	* 函数名   : InitHttp
	* 功能     : 初始化HTTP请求信息
	* 参数     : [IN]  lpHostName    : 远程主机名（例：host.codeages.com）
	             [IN]  lpMethod      : 请求方法（GET/POST）
	             [IN]  lpUrl         : 请求URL（例：/do.php?method=register）

	* 返回值   : [bool]True则初始化成功，False则初始化失败
	--------------------------------------------------------------------------------
	* 备注     : 使用POST方法时，所附带的请求参数以及值需要通过SendData函数完成
	* 典型用法 : InitHttp("host.codeages.com", "POST", "/register.php");
	             InitHttp("host.codeages.com", "GET", "/about.php");
	*******************************************************************************/
	inline bool InitHttp(__in char* lpHostName, __in char* lpMethod, __in char* lpUrl)
	{
		size_t szLen = 0;
		szLen = strlen(lpHostName);
		strncpy(CHttpTransfer::m_lpHostName, lpHostName, szLen);
	
		szLen = strlen(lpMethod);
		strncpy(CHttpTransfer::m_lpMethod, lpMethod, szLen);

		szLen = strlen(lpUrl);
		strncpy(CHttpTransfer::m_lpUrl, lpUrl, szLen);

		//初始化Winsock
		WSADATA wsaData;
		WORD wVersion = MAKEWORD(2, 2);

		if (::WSAStartup(wVersion, &wsaData) != 0)
		{
			return false;
		}
	
		//创建套接字
		CHttpTransfer::m_sckMain = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (CHttpTransfer::m_sckMain == INVALID_SOCKET)
		{
			return false;
		}

		//获取域名指向的IP地址
		struct hostent *lpHost;
		lpHost = gethostbyname(CHttpTransfer::m_lpHostName);
		if (lpHost == NULL)
		{
			return false;
		}

		//设置地址族信息
		m_addr.sin_family = AF_INET;
		m_addr.sin_addr = *((LPIN_ADDR)*lpHost->h_addr_list); 
		m_addr.sin_port = htons(80);

		//连接到远程主机
		if (::connect(m_sckMain, (SOCKADDR*)&m_addr, sizeof(m_addr)) == SOCKET_ERROR)
		{
			printf("error:%d", WSAGetLastError());
			return false;
		}

		return true;
	}


	/*! @SetRequestHeader
	********************************************************************************
	* 函数名   : SetRequestHeader
	* 功能     : 添加请求头
	* 参数     : [IN]  lpHeader      : 要添加的请求头名称
			     [IN]  lpValue       : 要添加的请求头值

	* 返回值   : [bool]True则添加成功成功，False则添加失败
	--------------------------------------------------------------------------------
	* 备注     : HTTP请求数据包默认已经包含HOST请求头，不必再添加
	* 典型用法 : SetRequestHeader("Accept-Encoding", "gzip, deflate");
	*******************************************************************************/
	inline bool SetRequestHeader(__in char* lpHeader, __in char* lpValue)
	{
		if (lpHeader == NULL || strlen(lpHeader) == 0)
		{
			return false;
		}

		char szTmp[nMAX_PACKET_LEN];
		memset(szTmp, 0, nMAX_PACKET_LEN);

		sprintf_s(szTmp, "%s", m_lpHeaders);
		sprintf_s(m_lpHeaders, "%s%s: %s\r\n", szTmp, lpHeader, lpValue);

		return true;
	}

	/*! @SendData
	********************************************************************************
	* 函数名   : SendData
	* 功能     : 发送数据到远程计算机
	* 参数     : [IN]  lpData        : 要发送的数据缓冲区（例："username=xxx&password=123456"）
			     [IN]  dwDataLen     : 要发送的数据缓冲区长度

	* 返回值   : [bool]True则发送成功，False则发送失败
	--------------------------------------------------------------------------------
	* 备注     : 发送GET请求的时候，lpData一般为空，dwDataLen则为0
	* 典型用法 : POST：SendData("username=xxx&password=123456", 28);
			     GET：SendData("", 0);
	*******************************************************************************/
	inline bool SendData(__in char* lpData, __in DWORD dwDataLen)
	{
		//构造数据包
		char lpPacket[nMAX_PACKET_LEN];
		memset(&lpPacket, 0, nMAX_PACKET_LEN);
		sprintf_s(lpPacket, nMAX_PACKET_LEN, "%s %s HTTP/1.1\r\nHost: %s\r\n%s\r\n%s", m_lpMethod, m_lpUrl, m_lpHostName, m_lpHeaders, lpData);

		//发送数据包
		printf("packets:%s", lpPacket);
		if (::send(m_sckMain, lpPacket, nMAX_PACKET_LEN, 0) == SOCKET_ERROR)
		{
			return false;
		}

		return true;
	}

	/*! @ReceiveData
	********************************************************************************
	* 函数名   : ReceiveData
	* 功能     : 接收来自远程计算机的数据
	* 参数     : [OUT]  lpBuffer      : 要接收的数据缓冲区
			     [IN]   dwBuffLen     : 要接收的数据缓冲区长度

	* 返回值   : [bool]True则接收成功，False则接收失败
	--------------------------------------------------------------------------------
	* 备注     : 无
	* 典型用法 : char lpBuff[4096];
			     memset(lpBuff, 0, 4096);
			     ReceiveData(lpBuff, 4096);
	*******************************************************************************/
	inline bool ReceiveData(__out char* lpBuffer, __in DWORD dwBuffLen)
	{
		//接收数据的临时缓冲区
		char *lpBuffTmp = new char[dwBuffLen];

		if (::recv(m_sckMain, lpBuffTmp, dwBuffLen, 0) == SOCKET_ERROR)
		{
			return false;
		}

		//获取Set-Cookie头，拷贝给m_lpCookies
		char szHeaderValue[nMAX_PACKET_LEN];
		memset(szHeaderValue, 0, nMAX_PACKET_LEN);
		CHttpTransfer::GetHeaderValue(lpBuffTmp, "Set-Cookie", CHttpTransfer::m_lpCookies);

		//把接收到的值拷贝给lpBuffer
		memcpy(lpBuffer, lpBuffTmp, dwBuffLen);

		delete [] lpBuffTmp;

		return true;
	}

	/*! @GetHeaderValue
	********************************************************************************
	* 函数名   : GetHeaderValue
	* 功能     : 获取接收到的数据包头信息
	* 参数     : [IN]   lpHeaders     : 数据包（由远程主机返回的数据包）
			     [IN]   lpHeaderName  : 数据包头名称（如：Set-Cookie）
				 [OUT]  lpValueBuffer : 接收数据包头信息值的缓冲区

	* 返回值   : [bool]True则获取成功，False则获取失败（原因一般为HTTP头不存在）
	--------------------------------------------------------------------------------
	* 备注     : 无
	* 典型用法 : char szHeaderValue[nMAX_PACKET_LEN];
	             memset(szHeaderValue, 0, nMAX_PACKET_LEN);  

				 //注：lpBuff是由服务器返回的数据包内容
	             GetHeaderValue(lpBuff, "Set-Cookie", szHeaderValue);
	*******************************************************************************/
	inline bool GetHeaderValue(__in char* lpHeaders, __in char* lpHeaderName, __out char* lpValueBuffer)
	{
		string strHeader(lpHeaders);
		string strSpec("\r\n");
		vector<string> vtHeaders;
		CHttpTransfer::split(strHeader, strSpec, vtHeaders);

		//遍历vector，找到lpHeaderName
		for (int i = 0; i != vtHeaders.size(); i++)
		{
			//获取lpHeaderName的长度
			size_t szHeaderLen = strlen(lpHeaderName);

			//找到头信息
			if (vtHeaders[i].substr(0, szHeaderLen) == lpHeaderName)
			{
				//截取头信息的值（除了头信息名称之外，还有一个冒号和空格，所以要+2）
				size_t szLen = szHeaderLen + 2;
				string strHeaderValue = vtHeaders[i].substr(szLen, vtHeaders[i].length());
				memcpy(lpValueBuffer, strHeaderValue.c_str(), strHeaderValue.size());
				return true;
			}
		}

		
		return false;
	}

	/*! @GetCookieValue
	********************************************************************************
	* 函数名   : GetCookieValue
	* 功能     : 获取cookie参数值
	* 参数     : [IN]   lpCookies     : 传入的Cookies信息缓冲区
			     [IN]   lpCookieName  : Cookies变量名
				 [OUT]  lpValue       : 接收Cookie变量值的缓冲区

	* 返回值   : [bool]True则获取成功，False则获取失败（原因一般为变量不存在）
	--------------------------------------------------------------------------------
	* 备注     : 无
	* 典型用法 : char szHeaderValue[nMAX_PACKET_LEN];
	             char szCookieValue[nMAX_PACKET_LEN];
	             memset(szHeaderValue, 0, nMAX_PACKET_LEN);
	             memset(szCookieValue, 0, nMAX_PACKET_LEN);
				 GetHeaderValue(lpBuff, "Set-Cookie", szHeaderValue);  //获取Cookies信息
	             GetCookieValue(szHeaderValue, "username", szCookieValue);
	*******************************************************************************/
	inline bool GetCookieValue(__in char* lpCookies, __in char* lpCookieName, __out char* lpValue)
	{
		string strCookies(lpCookies);
		string strSpec("; ");
		vector<string> vtCookie;   //储存cookie信息项

		CHttpTransfer::split(strCookies, strSpec, vtCookie);
		for (int i = 0; i != vtCookie.size(); i++)
		{
			//获取lpCookieName的长度
			size_t szCookieLen = strlen(lpCookieName);

			//找到cookie信息
			if (vtCookie[i].substr(0, szCookieLen) == lpCookieName)
			{
				//截取头信息的值（除了cookie名称之外，还有=号，所以要+1）
				size_t szLen = szCookieLen + 1;
				string strCookieValue = vtCookie[i].substr(szLen, vtCookie[i].length());
				memcpy(lpValue, strCookieValue.c_str(), strCookieValue.size());
				return true;
			}
		}

		return false;
	}

	/*! @CloseConnect
	********************************************************************************
	* 函数名   : CloseConnect
	* 功能     : 关闭、销毁一个连接
	* 参数     : 无

	* 返回值   : [bool]True则成功，False则失败
	--------------------------------------------------------------------------------
	* 备注     : 无
	* 典型用法 : CloseConnect();
	*******************************************************************************/
	inline bool CloseConnect(void)
	{
		if (::closesocket(m_sckMain) == false)
		{
			return false;
		}

		if (::WSACleanup() == SOCKET_ERROR)
		{
			return false;
		}

		return true;
	}

private:
	/*! @split
	********************************************************************************
	* 函数名   : split
	* 功能     : 把字符串分割为N部分，返回一个string类型的vector容器
	* 参数     : [IN]   src           : 要分割的字符串
			     [IN]   separator     : 分割符号
				 [OUT]  dest          : 返回接收分割结果的vector容器引用

	* 返回值   : void
	--------------------------------------------------------------------------------
	* 备注     : 无
	* 典型用法 : string strContent("a,b,c*f");
	             string strSpec(",");
				 vector<string> vtContent;
				 split(strContent, strSpec, vtContent);

				 //遍历输出vtContent的内容
				 for (int i = 0; i != vtContent.size(); i++)
				 {
					cout << vtContent[i] << endl;
				 }

				 结果：a
				       b
					   c*f
	*******************************************************************************/
	inline void split(const string& src, const string& separator, vector<string>& dest)
	{
		string str = src;
		string substring;
		string::size_type start = 0, index;

		do
		{
			index = str.find_first_of(separator,start);
			if (index != string::npos)
			{    
				substring = str.substr(start,index-start);
				dest.push_back(substring);
				start = str.find_first_not_of(separator,index);
				if (start == string::npos) return;
			}
		} while(index != string::npos);

		substring = str.substr(start);
		dest.push_back(substring);
	}

private:
	SOCKET m_sckMain;                     //socket主句柄
	sockaddr_in m_addr;                   //地址族对象
	char m_lpHostName[nMAX_URL_LEN];      //HTTP请求主机名
	char m_lpMethod[4];                   //请求方法
	char m_lpUrl[nMAX_URL_LEN];           //请求URL
	char m_lpHeaders[nMAX_PACKET_LEN];    //附加的请求头
	char m_lpCookies[nMAX_PACKET_LEN];    //服务器返回的Cookies

};

